package timing.ukulele.auth.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.beust.ah.A;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import timing.ukulele.auth.persistent.OAuth2AuthorizationConsentSimple;
import timing.ukulele.auth.persistent.OAuth2ClientScope;
import timing.ukulele.auth.persistent.RegisteredClientSimple;
import timing.ukulele.auth.service.OAuth2ClientScopeService;
import timing.ukulele.auth.service.RegisteredClientSimpleService;
import timing.ukulele.auth.vo.OAuth2AuthorizationConsentSimpleVO;
import timing.ukulele.auth.vo.OAuth2ClientScopeVO;
import timing.ukulele.auth.vo.RegisteredClientSimpleVO;
import timing.ukulele.common.data.ResponseData;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@RequestMapping("/registerClient")
@RestController
@Tag(name = "OAuth2客户端", description = "只能由开发人员操作")
@ApiResponses(value = {
        @ApiResponse(responseCode = "1000", description = "成功", content = {@Content(mediaType = "application/json")}),
        @ApiResponse(responseCode = "1001", description = "失败", content = {@Content(mediaType = "application/json")}),
        @ApiResponse(responseCode = "3000", description = "业务处理失败", content = {@Content(mediaType = "application/json")})
})
public class RegisteredClientController {

    private final RegisteredClientRepository registeredClientRepository;
    private final RegisteredClientSimpleService simpleService;
    private final OAuth2ClientScopeService scopeService;
    private final PasswordEncoder passwordEncoder;

    public RegisteredClientController(RegisteredClientRepository registeredClientRepository,
                                      RegisteredClientSimpleService simpleService,
                                      OAuth2ClientScopeService scopeService,
                                      PasswordEncoder passwordEncoder) {
        this.registeredClientRepository = registeredClientRepository;
        this.simpleService = simpleService;
        this.scopeService = scopeService;
        this.passwordEncoder = passwordEncoder;
    }

    @Operation(summary = "根据id获取client", description = "根据id获取client")
    @GetMapping("/id")
    public ResponseData<RegisteredClientSimpleVO> getById(@RequestParam("id") String id) {
        RegisteredClient client = this.registeredClientRepository.findById(id);
        return ResponseData.success(createVo(client));
    }

    @Operation(summary = "根据id获取client", description = "根据id获取client")
    @GetMapping("/clientId")
    public ResponseData<RegisteredClientSimpleVO> getByClientId(String clientId) {
        RegisteredClient client = this.registeredClientRepository.findByClientId(clientId);
        return ResponseData.success(createVo(client));
    }

    private RegisteredClientSimpleVO createVo(RegisteredClient client) {
        if (client == null) return null;
        RegisteredClientSimpleVO vo = new RegisteredClientSimpleVO();
        vo.setId(client.getId());
        vo.setClientId(client.getClientId());
        vo.setClientSecret(client.getClientSecret());
        vo.setClientName(client.getClientName());
        if (client.getClientIdIssuedAt() != null) {
            vo.setClientIdIssuedAt(LocalDateTime.from(client.getClientIdIssuedAt()));
        }
        if (client.getClientSecretExpiresAt() != null) {
            vo.setClientSecretExpiresAt(LocalDateTime.from(client.getClientSecretExpiresAt()));
        }
        if (!CollectionUtils.isEmpty(client.getClientAuthenticationMethods())) {
            vo.setClientAuthenticationMethods(client.getClientAuthenticationMethods().stream().map(ClientAuthenticationMethod::getValue).collect(Collectors.toSet()));
        }
        if (!CollectionUtils.isEmpty(client.getAuthorizationGrantTypes())) {
            vo.setAuthorizationGrantTypes(client.getAuthorizationGrantTypes().stream().map(AuthorizationGrantType::getValue).collect(Collectors.toSet()));
        }
        if (!CollectionUtils.isEmpty(client.getRedirectUris())) {
            vo.setRedirectUris(String.join(",", client.getRedirectUris()));
        }
        vo.setScopes(client.getScopes());
        vo.setPostLogoutRedirectUris(String.join(",", client.getPostLogoutRedirectUris()));

        // 设置信息
        vo.setRequireAuthorizationConsent(client.getClientSettings().isRequireAuthorizationConsent());
        vo.setAccessTokenTimeToLive(client.getTokenSettings().getAccessTokenTimeToLive().toHours());
        vo.setAuthorizationCodeTimeToLive(client.getTokenSettings().getAuthorizationCodeTimeToLive().toMinutes());
        vo.setDeviceCodeTimeToLive(client.getTokenSettings().getDeviceCodeTimeToLive().toMinutes());
        vo.setRefreshTokenTimeToLive(client.getTokenSettings().getRefreshTokenTimeToLive().toDays());
        vo.setReuseRefreshTokens(client.getTokenSettings().isReuseRefreshTokens());

        return vo;
    }

    @PostMapping
    @Operation(summary = "创建", description = "创建一个客户端")
    public ResponseData<Boolean> create(@RequestBody RegisteredClientSimpleVO vo) {
        // 检查数据的完整性
        if (StringUtils.hasText(vo.getId())) {
            return ResponseData.paramError("id不能有值");
        }
        if (!(StringUtils.hasText(vo.getRedirectUris())
                && StringUtils.hasText(vo.getClientId())
                && StringUtils.hasText(vo.getClientName())
                && StringUtils.hasText(vo.getClientSecret()))) {
            return ResponseData.paramError("clientId,clientName,clientSecret必须有值");
        }
        if (CollectionUtils.isEmpty(vo.getScopes())
                || CollectionUtils.isEmpty(vo.getClientAuthenticationMethods())
                || CollectionUtils.isEmpty(vo.getAuthorizationGrantTypes())) {
            return ResponseData.paramError("scopes,clientAuthenticationMethods,authorizationGrantTypes,redirectUris必须有值");
        }

        if (vo.getRequireAuthorizationConsent() == null
                || vo.getAccessTokenTimeToLive() == null
                || vo.getAuthorizationCodeTimeToLive() == null
                || vo.getDeviceCodeTimeToLive() == null
                || vo.getRefreshTokenTimeToLive() == null) {
            return ResponseData.paramError("requireAuthorizationConsent,accessTokenTimeToLive,authorizationCodeTimeToLive,deviceCodeTimeToLive,refreshTokenTimeToLive必须有值");
        }
        RegisteredClient po = registeredClientRepository.findByClientId(vo.getClientId());
        if (po != null) {
            return ResponseData.paramError("该id已存在");
        }
        // 默认需要授权确认
        ClientSettings.Builder clientSettingBuilder = ClientSettings.builder()
                .requireAuthorizationConsent(vo.getRequireAuthorizationConsent());
        TokenSettings.Builder tokenSettingsBuilder = TokenSettings.builder()
                // 自包含token(jwt)
                .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
                // Access Token 存活时间：2小时
                .accessTokenTimeToLive(Duration.ofHours(vo.getAccessTokenTimeToLive()))
                // 授权码存活时间：5分钟
                .authorizationCodeTimeToLive(Duration.ofMinutes(vo.getAuthorizationCodeTimeToLive()))
                // 设备码存活时间：5分钟
                .deviceCodeTimeToLive(Duration.ofMinutes(vo.getDeviceCodeTimeToLive()))
                // Refresh Token 存活时间：7天
                .refreshTokenTimeToLive(Duration.ofDays(vo.getRefreshTokenTimeToLive()))
                // 刷新 Access Token 后是否重用 Refresh Token
                .reuseRefreshTokens(vo.getReuseRefreshTokens())
                // 设置 Id Token 加密方式
                .idTokenSignatureAlgorithm(SignatureAlgorithm.RS256);

        // ClientAuthenticationMethod
        List<ClientAuthenticationMethod> methodList = new ArrayList<>(vo.getClientAuthenticationMethods().size());
        ClientAuthenticationMethod md;
        for (String method : vo.getClientAuthenticationMethods()) {
            md = new ClientAuthenticationMethod(method);
            methodList.add(md);
        }
        List<AuthorizationGrantType> typeList = new ArrayList<>(vo.getAuthorizationGrantTypes().size());
        AuthorizationGrantType tp;
        for (String type : vo.getAuthorizationGrantTypes()) {
            tp = new AuthorizationGrantType(type);
            typeList.add(tp);
        }

        RegisteredClient.Builder builder = RegisteredClient.withId(vo.getClientId())
                // 客户端id
                .clientId(vo.getClientId())
                // 客户端名称
                .clientName(vo.getClientName())
                // 客户端秘钥，使用密码解析器加密
                .clientSecret(passwordEncoder.encode(vo.getClientSecret()))
                // 客户端认证方式，基于请求头的认证
                .clientAuthenticationMethods(methods -> methods.addAll(methodList))
                // 配置资源服务器使用该客户端获取授权时支持的方式
                .authorizationGrantTypes(types -> types.addAll(typeList))
                // 该客户端的授权范围，OPENID与PROFILE是IdToken的scope，获取授权时请求OPENID的scope时认证服务会返回IdToken
                .scopes(scopes -> scopes.addAll(vo.getScopes()))
                // 客户端设置，设置用户需要确认授权
                .clientSettings(clientSettingBuilder.build())
                // token相关配置
                .tokenSettings(tokenSettingsBuilder.build());
        if (StringUtils.hasText(vo.getRedirectUris())) {
            // 授权码模式回调地址，oauth2.1已改为精准匹配，不能只设置域名，并且屏蔽了localhost，本机使用127.0.0.1访问
            builder.redirectUris(uris -> uris.addAll(Arrays.stream(vo.getRedirectUris().split(",")).toList()));
        }
        if (vo.getClientIdIssuedAt() != null) {
            builder.clientIdIssuedAt(Instant.ofEpochSecond(vo.getClientIdIssuedAt().getSecond()));
        }
        if (vo.getClientSecretExpiresAt() != null) {
            builder.clientSecretExpiresAt(Instant.ofEpochSecond(vo.getClientSecretExpiresAt().getSecond()));
        }
        if (StringUtils.hasText(vo.getPostLogoutRedirectUris())) {
            builder.postLogoutRedirectUris(uris -> uris.addAll(Arrays.stream(vo.getPostLogoutRedirectUris().split(",")).toList()));
        }
        RegisteredClient registeredClient = builder.build();
        registeredClientRepository.save(registeredClient);
        return ResponseData.success(Boolean.TRUE);
    }

    @Operation(summary = "客户端分页列表", description = "客户端分页列表")
    @Parameters({
            @Parameter(name = "name", description = "客户端名称，模糊搜索", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "current", description = "当前页码", required = true, in = ParameterIn.PATH),
            @Parameter(name = "size", description = "每页大小", required = true, in = ParameterIn.PATH)
    })
    @GetMapping("page/{current}/{size}")
    public ResponseData<IPage<RegisteredClientSimpleVO>> page(
            @RequestParam(value = "name", required = false) String name,
            @PathVariable("current") int current, @PathVariable("size") int size) {
        if (size == 0) {
            size = 10;
        }
        if (current == 0) {
            current = 1;
        }
        RegisteredClientSimple filter = new RegisteredClientSimple();
        filter.setClientName(name);
        IPage<RegisteredClientSimple> page = simpleService.getPage(filter, current, size);
        if (page == null || CollectionUtils.isEmpty(page.getRecords())) {
            return ResponseData.success(new Page<>(current, size));
        }
        Page<RegisteredClientSimpleVO> voPage = new Page<>(current, size);
        List<RegisteredClientSimpleVO> voList = new ArrayList<>(page.getRecords().size());
        page.getRecords().forEach(po -> {
            RegisteredClientSimpleVO vo = new RegisteredClientSimpleVO();
            BeanUtils.copyProperties(po, vo);
            if (StringUtils.hasText(po.getClientAuthenticationMethods())) {
                String[] clientAuthenticationMethods = po.getClientAuthenticationMethods().split(",");
                vo.setClientAuthenticationMethods(new HashSet<>(List.of(clientAuthenticationMethods)));
            }
            if (StringUtils.hasText(po.getAuthorizationGrantTypes())) {
                String[] authorizationGrantTypes = po.getAuthorizationGrantTypes().split(",");
                vo.setAuthorizationGrantTypes(new HashSet<>(List.of(authorizationGrantTypes)));
            }
            if (StringUtils.hasText(po.getScopes())) {
                String[] scopes = po.getScopes().split(",");
                vo.setScopes(new HashSet<>(List.of(scopes)));
            }
            RegisteredClient client = registeredClientRepository.findByClientId(po.getClientId());
            if (client != null) {
                if (client.getClientSettings() != null) {
                    vo.setRequireAuthorizationConsent(client.getClientSettings().isRequireAuthorizationConsent());
                }
                if (client.getTokenSettings() != null) {
                    if (client.getTokenSettings().getRefreshTokenTimeToLive() != null)
                        vo.setRefreshTokenTimeToLive(client.getTokenSettings().getRefreshTokenTimeToLive().toDays());
                    vo.setReuseRefreshTokens(client.getTokenSettings().isReuseRefreshTokens());
                    if (client.getTokenSettings().getAccessTokenTimeToLive() != null) {
                        vo.setAccessTokenTimeToLive(client.getTokenSettings().getAccessTokenTimeToLive().toHours());
                    }
                    if (client.getTokenSettings().getDeviceCodeTimeToLive() != null) {
                        vo.setDeviceCodeTimeToLive(client.getTokenSettings().getDeviceCodeTimeToLive().toMinutes());
                    }
                    if (client.getTokenSettings().getRefreshTokenTimeToLive() != null) {
                        vo.setRefreshTokenTimeToLive(client.getTokenSettings().getRefreshTokenTimeToLive().toDays());
                    }
                    if (client.getTokenSettings().getAuthorizationCodeTimeToLive() != null) {
                        vo.setAuthorizationCodeTimeToLive(client.getTokenSettings().getAuthorizationCodeTimeToLive().toMinutes());
                    }
                }
            }

            voList.add(vo);
        });
        voPage.setPages(page.getPages());
        voPage.setTotal(page.getTotal());
        voPage.setRecords(voList);
        return ResponseData.success(voPage);
    }

    @Operation(summary = "当前用户授权的客户端", description = "当前用户授权了哪些客户端，授权范围是什么")
    @Parameters({
            @Parameter(name = "X-USER", description = "当前用户名", required = true, in = ParameterIn.HEADER),
            @Parameter(name = "current", description = "当前页码", required = true, in = ParameterIn.PATH),
            @Parameter(name = "size", description = "每页大小", required = true, in = ParameterIn.PATH)
    })
    @GetMapping("/user/consent/{current}/{size}")
    public ResponseData<IPage<OAuth2AuthorizationConsentSimpleVO>> userConsent(@RequestHeader("X-USER") String username, @PathVariable("current") int current, @PathVariable("size") int size) {
        IPage<OAuth2AuthorizationConsentSimple> page = simpleService.getUserConsentPage(username, current, size);
        if (page == null || CollectionUtils.isEmpty(page.getRecords())) {
            return ResponseData.success(new Page<>(current, size));
        }
        List<RegisteredClientSimple> clientList = simpleService.lambdaQuery().
                in(RegisteredClientSimple::getClientId, page.getRecords().stream().map(OAuth2AuthorizationConsentSimple::getRegisteredClientId).collect(Collectors.toSet()))
                .select(RegisteredClientSimple::getClientId, RegisteredClientSimple::getClientName).list();
        if (CollectionUtils.isEmpty(clientList)) {
            return ResponseData.success(new Page<>(current, size));
        }
        Map<String, String> clientMap = clientList.stream().collect(Collectors.toMap(RegisteredClientSimple::getClientId, RegisteredClientSimple::getClientName));
        return buildOAuth2AuthorizationConsent(page, current, size, null, clientMap);
    }

    @DeleteMapping("/user/consent")
    public ResponseData<Boolean> deleteUserConsent(@RequestHeader("X-USER") String username, @RequestParam("clientId") String clientId) {
        Boolean success = this.simpleService.deleteUserConsent(username, clientId);
        return ResponseData.success(success);
    }

    @Operation(summary = "哪些用户向该客户端授权", description = "哪些用户向该客户端授权，授权范围是什么")
    @Parameters({
            @Parameter(name = "clientId", description = "客户端id", required = true, in = ParameterIn.QUERY),
            @Parameter(name = "current", description = "当前页码", required = true, in = ParameterIn.PATH),
            @Parameter(name = "size", description = "每页大小", required = true, in = ParameterIn.PATH)
    })
    @GetMapping("/client/consent/{current}/{size}")
    public ResponseData<IPage<OAuth2AuthorizationConsentSimpleVO>> clientConsent(@RequestParam("clientId") String clientId, @PathVariable("current") int current, @PathVariable("size") int size) {
        RegisteredClientSimple client = simpleService.lambdaQuery().
                eq(RegisteredClientSimple::getClientId, clientId)
                .select(RegisteredClientSimple::getClientId, RegisteredClientSimple::getClientName).one();
        if (client == null) {
            return ResponseData.success(new Page<>(current, size));
        }
        IPage<OAuth2AuthorizationConsentSimple> page = simpleService.getClientConsentUserPage(clientId, current, size);

        return buildOAuth2AuthorizationConsent(page, current, size, client, null);
    }

    private ResponseData<IPage<OAuth2AuthorizationConsentSimpleVO>> buildOAuth2AuthorizationConsent(IPage<OAuth2AuthorizationConsentSimple> page, int current, int size,
                                                                                                    RegisteredClientSimple client, Map<String, String> clientMap) {
        if (page == null || CollectionUtils.isEmpty(page.getRecords())) {
            return ResponseData.success(new Page<>(current, size));
        }
        List<OAuth2ClientScope> list = scopeService.list();
        if (CollectionUtils.isEmpty(list)) {
            return ResponseData.success(new Page<>(current, size));
        }
        Map<String, String> scopeMap = list.stream().collect(Collectors.toMap(OAuth2ClientScope::getName, OAuth2ClientScope::getDescription));
        Page<OAuth2AuthorizationConsentSimpleVO> voPage = new Page<>(current, size);
        List<OAuth2AuthorizationConsentSimpleVO> voList = new ArrayList<>(page.getRecords().size());
        voPage.setPages(page.getPages());
        voPage.setTotal(page.getTotal());
        voPage.setRecords(voList);


        OAuth2AuthorizationConsentSimpleVO vo;
        for (OAuth2AuthorizationConsentSimple record : page.getRecords()) {
            vo = new OAuth2AuthorizationConsentSimpleVO();
            voList.add(vo);
            vo.setRegisteredClientId(record.getRegisteredClientId());
            vo.setPrincipalName(record.getPrincipalName());
            if (client != null) {
                vo.setRegisteredClientName(client.getClientName());
            } else if (!CollectionUtils.isEmpty(clientMap)) {
                String clientName = clientMap.get(record.getRegisteredClientId());
                vo.setRegisteredClientName(clientName);
            }
            if (!StringUtils.hasText(record.getAuthorities())) {
                continue;
            }
            String[] scopes = record.getAuthorities().split(",");
            List<OAuth2ClientScopeVO> scopeVOList = new ArrayList<>(scopes.length);
            OAuth2ClientScopeVO scopeVO;
            for (String scope : scopes) {
                String s = scope.replace("SCOPE_", "");
                String scopeDesc = scopeMap.get(s);
                scopeVO = new OAuth2ClientScopeVO();
                scopeVO.setName(s);
                scopeVO.setDescription(scopeDesc);
                scopeVOList.add(scopeVO);
            }
            vo.setAuthorities(scopeVOList);
        }
        return ResponseData.success(voPage);
    }
}
